//
//  DemoViewController.m
//  CH02_Example01
//
//  Created by 葛 剑峰 on 12-6-6.
//  Copyright (c) 2012年 Dream and Talent. All rights reserved.
//

#import "DemoViewController.h"
#import "Global.h"

const float vVertices[] = {
     0.0f,  0.5f, 0.0f,
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f
};

@interface DemoViewController ()
{
    GLuint _program;
}

@property (nonatomic, strong) EAGLContext *context;

-(void)setupGL;
-(void)tearDownGL;

-(BOOL)loadShaders;
-(BOOL)compileShader:(GLuint*)shader type:(GLenum)type file:(NSString*)file;
-(BOOL)linkProgram:(GLuint)prog;
-(BOOL)validateProgram:(GLuint)prog;

@end

@implementation DemoViewController

@synthesize context = _context;

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
    
    self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    
    if (!self.context) {
        NSLog(@"Failed to create the OpenGL ES 2.0 context.");
    }
    
    GLKView *view = (GLKView*)self.view;
    view.context = self.context;
    
    [self setupGL];
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    
    [self tearDownGL];
    
    if ([EAGLContext currentContext] == self.context) {
        [EAGLContext setCurrentContext:nil];
    }
    self.context = nil;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}

#pragma mark -
#pragma mark GLKViewDelegate

-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
    
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    
    glUseProgram(_program);
    
    glVertexAttribPointer(ATTRIB_VERTEX, 3, GL_FLOAT, GL_FALSE, 0, vVertices);
    glEnableVertexAttribArray(ATTRIB_VERTEX);
    
    glDrawArrays(GL_TRIANGLES, 0, 3);
}

#pragma mark -
#pragma mark GLKViewControllerDelegate

-(void)update
{
    
}

#pragma mark -
#pragma mark OpenGL ES 2.0 Initialization and Destruction

-(void)setupGL
{
    [EAGLContext setCurrentContext:self.context];
    
    [self loadShaders];
}

-(void)tearDownGL
{
    [EAGLContext setCurrentContext:self.context];
    
    if (_program) {
        glDeleteProgram(_program);
        _program = 0;
    }
}

#pragma mark -
#pragma mark OpenGL ES 2.0 Implementation

-(BOOL)loadShaders
{
    GLuint vertShader, fragShader;
    NSString *vertShaderPathname, *fragShaderPathname;
    
    // Create the shader program.
    _program = glCreateProgram();
    if (!_program) {
        NSLog(@"Failed to create the shader program.");
        return NO;
    }
    
    // Get the vertex shader file path. Create and compile the vertex shader.
    vertShaderPathname = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"vsh"];
    if (![self compileShader:&vertShader type:GL_VERTEX_SHADER file:vertShaderPathname]) {
        NSLog(@"Failed to create and compile the vertex shader.");
        return NO;
    }
    
    // Get the fragment shader file path. Create and compile the fragment shader.
    fragShaderPathname = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"fsh"];
    if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:fragShaderPathname]) {
        NSLog(@"Failed to create and compile the fragment shader.");
        return NO;
    }
    
    // Attach the vertex and fragment shader to shader program.
    glAttachShader(_program, vertShader);
    glAttachShader(_program, fragShader);
    
    // Bind the data to shader attributes.
    glBindAttribLocation(_program, ATTRIB_VERTEX, "position");
    
    // Link the shader program
    if (![self linkProgram:_program]) {
        NSLog(@"Failed to link the program.");
        
        if (vertShader) {
            glDeleteShader(vertShader);
            vertShader = 0;
        }
        
        if (fragShader) {
            glDeleteShader(fragShader);
            fragShader = 0;
        }
        
        if (_program) {
            glDeleteProgram(_program);
            _program = 0;
        }
        
        return NO;
    }
    
    if (vertShader) {
        glDetachShader(_program, vertShader);
        glDeleteShader(vertShader);
        vertShader = 0;
    }
    
    if (fragShader) {
        glDetachShader(_program, fragShader);
        glDeleteShader(fragShader);
        fragShader = 0;
    }
    
    return YES;
}

-(BOOL)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file
{
    GLint status;
    const GLchar *source;
    
    // Convert the shader file path from NSString to GLchar (UTF8 format)
    source = (GLchar*)[[NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil]UTF8String];
    if (!source) {
        NSLog(@"Failed to convert the shader file path.");
        return NO;
    }
    
    // Create the shader
    *shader = glCreateShader(type);
    if (!shader) {
        NSLog(@"Failed to create the shader.");
        return NO;
    }
    
    // Get the shader source code
    glShaderSource(*shader, 1, &source, NULL);
    
    // Compile the shader source code
    glCompileShader(*shader);
    
    // Get shader compile status
    glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
    
    // Check the shader compile status. If it is NO, output the shader compile log.
    if (!status) {
        GLint logLength;
        glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
        if (logLength > 0) {
            GLchar *log = (GLchar*)malloc(logLength);
            glGetShaderInfoLog(*shader, logLength, &logLength, log);
            NSLog(@"Shader compile log: %s\n", log);
            free(log);
        }
        return NO;
    }
    
    return YES;    
}

-(BOOL)linkProgram:(GLuint)prog
{
    GLint status;
    
    // Link the shader program
    glLinkProgram(prog);
    
    // Get the shader program link status
    glGetProgramiv(prog, GL_LINK_STATUS, &status);
    
    // Check the shader program link status. If it is NO, output the shader program link log.
    if (!status) {
        GLint logLength;
        glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
        if (logLength > 0) {
            GLchar *log = (GLchar*)malloc(logLength);
            glGetProgramInfoLog(prog, logLength, &logLength, log);
            NSLog(@"Program validate log: %s\n", log);
            free(log);
        }
        return NO;
    }
    
    return YES;
}

-(BOOL)validateProgram:(GLuint)prog
{
    GLint status;
    
    // Validate the shader program
    glValidateProgram(prog);
    
    // Get the shader program validate status
    glGetProgramiv(prog, GL_VALIDATE_STATUS, &status);
    
    // Check the shader program validate status. If it is NO, output the shader program validate log.
    if (!status) {
        GLint logLength;
        glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
        if (logLength > 0) {
            GLchar *log = (GLchar*)malloc(logLength);
            glGetProgramInfoLog(prog, logLength, &logLength, log);
            NSLog(@"Program validate log: %s\n", log);
            free(log);
        }
        return NO;
    }
    
    return YES;
}

@end
